﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CCTV.Common;
using CCTV.SerialPorts;
using System.Web;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace CCTV.Logic
{
    public static class CommandManager
    {
        #region declarations
        
        #region configuration declarations
        
        private static volatile CameraXML _conf = null;
        private static CameraXML config
        {
            get
            {
                if (_conf == null)
                {
                    _conf = CameraXML.ReadFromFile(configPath);
                    ReadConfig(_conf); //initiate all of the ports...
                }
                return _conf;
            }
            set
            {
                _conf = value;
            }

        }

        static volatile Dictionary<ushort, SerialPortBase> _configuredCameraSerialPorts = null;
        static Dictionary<ushort, SerialPortBase> configuredCameraSerialPorts
        {
            get
            {
                if (_configuredCameraSerialPorts == null)
                {
                    ReadConfig(config);
                }
                return _configuredCameraSerialPorts;
            }
        }
        static volatile SerialPortBase _mainSystemPort = null;
        static SerialPortBase mainSystemPort
        {
            get
            {
                if (_mainSystemPort == null)
                {
                    ReadConfig(config);
                }
                return _mainSystemPort;
            }
        }
       


        private static string configPath
        {
            get
            {
                return HttpContext.Current.Server.MapPath("~/App_Data/conf.xml");
            }
        }
        #endregion

        #region video declarations
        private static volatile int videoCameraIndex = 0; // zero based index of video capture device to use
        private static volatile int videoFrameRate = 25;  // Depends on video device caps.  Generally 4-30.
        private static volatile int currentVideoWidth = 320;
        private static volatile int currentVideoHeight = 240;
        private static long currentVideoCompression = 50;
        private static Bitmap lastImage = new Bitmap(340, 240);
        //private static Capture cameraDevice = null;

        private static bool resolutionChangeOnItsWay = false;
        private static bool userHasStoppedCamera = false;

        private static Dictionary<AppConstants.VideoSizeEnum, Rectangle> videoSizes = new Dictionary<AppConstants.VideoSizeEnum, Rectangle>
        {
            {AppConstants.VideoSizeEnum.Large, new Rectangle() { X = 640, Y = 480 }},
            {AppConstants.VideoSizeEnum.Medium, new Rectangle() { X = 320, Y = 240 }},
            {AppConstants.VideoSizeEnum.Small, new Rectangle() { X = 160, Y = 120 }}
        };

        private static Dictionary<AppConstants.VideoCompressionEnum, long> videoCompressions = new Dictionary<AppConstants.VideoCompressionEnum, long>
        {
             {AppConstants.VideoCompressionEnum.Bad, 30},
             {AppConstants.VideoCompressionEnum.Medium, 50},
             {AppConstants.VideoCompressionEnum.Good, 70},
             {AppConstants.VideoCompressionEnum.Best, 90}
        };

        static MemoryStream ms;

        #endregion

        #endregion


        #region security related
        public static List<ApplicationUser> GetApplicationUsers()
        {
            return config.Users;
        }

        public static void AddApplicationUser(string usr, string pass)
        {
            config.Users.Add(new ApplicationUser(usr, pass));
            //serialize it...
            CameraXML.WriteToFile(configPath, config);
        }

        public static void DeleteApplicationUser(string usr)
        {
            ApplicationUser usrToDelete = config.Users.Where(x => x.UserName == usr).FirstOrDefault();
            if (!usrToDelete.IsAdmin)
            {
                config.Users.Remove(usrToDelete);
                //serialize it...
                CameraXML.WriteToFile(configPath, config);
            }
        }

        public static CameraXML GetConfig()
        {
            return config;
        }

        private static CameraXML GetTestData()
        {
            CameraXML retVal = new CameraXML() { SelectedCamera = 3, Cameras = new List<CCTVCamera>(), IsDebug = true };
            for (ushort i = 0; i < 8; i++)
            {
                CCTVCamera camToShow = new CCTVCamera();
                camToShow.PresetPositions = new List<CCTVPreset>();
                for (int j = 0; j < 10; j++)
                {
                    camToShow.PresetPositions.Add(new CCTVPreset(j, "position " + j.ToString()));
                }
                camToShow.CameraName = "Camera #" + i.ToString();
                camToShow.CameraNumber = i;
                camToShow.SerialPort4Control = "COM"+i.ToString();
                camToShow.BaudRate = i * 1000+1;
                camToShow.MinMovementSpeed = i + 1;
                camToShow.MaxMovementSpeed = 255 - i * 3;
                camToShow.DefaultMovementSpeed = 75;
                camToShow.IncrementStepMovementSpeed = 15 - i;
                camToShow.CameraType = AppConstants.CommandType.PelcoD;
                retVal.Cameras.Add(camToShow);
            }
            return retVal;
        }

        #endregion

        #region admin commands
        public static void SwitchToCamera(ushort camNumber)
        {
            CustomPICBoardPort.Instance.SendCommand(AppConstants.CustomPICBoardCommandsEnum.CamValue, GetCameraFromId(camNumber).CameraVideoSwitchCommand, mainSystemPort);
        }

        public static void InterpretAdminCommand(string cmd)
        {
            AppConstants.CustomPICBoardCommandsEnum defcmd = AppConstants.CustomPICBoardCommandsEnum.Undefined;
            if (Enum.TryParse<AppConstants.CustomPICBoardCommandsEnum>(cmd, out defcmd))
            {
                switch (defcmd)
                {
                    case AppConstants.CustomPICBoardCommandsEnum.On:
                        SwitchOnCameras();
                        break;
                    case AppConstants.CustomPICBoardCommandsEnum.Off:
                        SwitchOffCameras();
                        break;
                    default:
                        break;
                }
            }
        }

        private static void SwitchOnCameras()
        {
            CustomPICBoardPort.Instance.SendCommand(AppConstants.CustomPICBoardCommandsEnum.On, config.CamerasRS232TurnOnCommand, mainSystemPort);
        }

        private static void SwitchOffCameras()
        {
            CustomPICBoardPort.Instance.SendCommand(AppConstants.CustomPICBoardCommandsEnum.Off, config.CamerasRS232TurnOffCommand, mainSystemPort);
        }

        public static void TurnOffEntireSystem()
        {
            //turn off all cameras...
            SwitchOffCameras();
            //close all serialPorts...
            mainSystemPort.Close();
            StopCam(); //close the camera capture device...
            foreach (SerialPortBase sp in configuredCameraSerialPorts.Values)
            {
                sp.Close();
            }
            //TO DO: call windows service to shut the system down...
        }

        #endregion

       
        #region camera commands
        public static void InterpretCameraCommand(ushort camNumber, string command, uint camSpeed)
        {
            KeepAliveUser();
            CCTVCamera selectedCam = GetCameraFromId(camNumber);
            GetSerialManagerForProtocol(selectedCam.CameraType).SendCommand(command, selectedCam.CameraNumber, camSpeed, configuredCameraSerialPorts[camNumber]);
        }
        #endregion

        #region video related
        public static void ChangeVideoSize(string size)
        {
            KeepAliveUser();
            if (userHasStoppedCamera)
                return;

            AppConstants.VideoSizeEnum sz = AppConstants.VideoSizeEnum.Medium;
            if (Enum.TryParse<AppConstants.VideoSizeEnum>(size, out sz))
            {
                resolutionChangeOnItsWay = true; //flag it...
                currentVideoHeight = videoSizes[sz].Y;
                currentVideoWidth = videoSizes[sz].X;
                Video.VideoCapture.CloseVideoSource();
                Video.VideoCapture.StartCapture(currentVideoWidth, currentVideoHeight);
                resolutionChangeOnItsWay = false;//re-allow capture again..
            }
        }

        public static void ChangeVideoCompression(string value)
        {
            KeepAliveUser();
            if (userHasStoppedCamera)
                return;

            AppConstants.VideoCompressionEnum compr = AppConstants.VideoCompressionEnum.Good;
            if (Enum.TryParse<AppConstants.VideoCompressionEnum>(value, out compr))
            {
                currentVideoCompression = videoCompressions[compr];
            }
        }

        public static void VideoCommand(string cmd)
        {
            KeepAliveUser();
            AppConstants.VideoCommandsEnum func = AppConstants.VideoCommandsEnum.Play;
            if (Enum.TryParse<AppConstants.VideoCommandsEnum>(cmd, out func))
            {
                switch (func)
                {
                    case AppConstants.VideoCommandsEnum.Play:
                        PlayCam();
                        break;
                    case AppConstants.VideoCommandsEnum.Stop:
                        StopCam();
                        break;
                    case AppConstants.VideoCommandsEnum.Pause:
                        //PauseCam();
                        break;
                    default:
                        break;
                }
            }
        }

        private static void StopCam()
        {   
            Video.VideoCapture.CloseVideoSource();
            userHasStoppedCamera = true;
        }

        private static void PlayCam()
        {
            Video.VideoCapture.StartCapture(currentVideoWidth, currentVideoHeight);
            userHasStoppedCamera = false;
        }

        private static void PauseCam()
        {
        }

        public static byte[] GetPicture()
        {

            KeepAliveUser();

            if (resolutionChangeOnItsWay || userHasStoppedCamera) //pause, stop or res change should return nothing...
                return null; //we are currently changing the resolution, so bug off...

            if (lastImage != null)
                lastImage.Dispose();

            lock (Video.VideoCapture.LastImage)
            {
                lastImage = (Bitmap)Video.VideoCapture.LastImage.Clone();
            }
            if (ms != null)
                ms.Dispose();

            ms = new MemoryStream();
            lastImage.Save(ms, ImageFormat.Jpeg);
            lastImage.Dispose();

            byte[] retVal = ms.ToArray();
            ms.Close();
            ms.Dispose();
            return retVal;

        }
        #endregion

        private static void ReadConfig(CameraXML xml)
        {
            config = xml;

            //first, initiate the relay board

            _mainSystemPort = new SerialPortBase();
            mainSystemPort.Init(xml.HardwareInterfacePort, xml.HardwareInterfaceBaudRate, config.IsDebug);
            mainSystemPort.Open();

            _configuredCameraSerialPorts = new Dictionary<ushort, SerialPortBase>();
                
            lock (_configuredCameraSerialPorts)
            {
                //initiate serial ports...
                //next, get all of the ports for the cameras...
                foreach (CCTVCamera cam in xml.Cameras)
                {
                    List<SerialPortBase> ports = (from p in configuredCameraSerialPorts.Values
                                                  select p).ToList();

                    if (cam.SerialPort4Control == null)
                    {
                        //this camera has no port for control... (static camera?)
                        configuredCameraSerialPorts.Add(cam.CameraNumber, null);
                        continue;
                    }

                    //if a port is defined in the config file, see if it's a mutual one (another camera uses it also)
                    if (ports.Where(x => x != null && x.PortName == cam.SerialPort4Control).Any())
                    {
                        //port already defined... 
                        //re-add it to this camera also
                        configuredCameraSerialPorts.Add(cam.CameraNumber, configuredCameraSerialPorts.Where(x => x.Value.PortName == cam.SerialPort4Control).FirstOrDefault().Value);
                        continue;
                    }
                    //otherwise, the port is a new one, create a new entry in the dictionary
                    SerialPortBase sp = new SerialPortBase();
                    sp.Init(cam.SerialPort4Control, cam.BaudRate, config.IsDebug);
                    sp.Open();
                    configuredCameraSerialPorts.Add(cam.CameraNumber, sp);
                }
            }
        }

        public static CCTVCamera GetCameraFromId(ushort cameraNumber)
        {
            return config.Cameras.Where(x => x.CameraNumber == cameraNumber).FirstOrDefault();
        }

        private static void KeepAliveUser()
        {
            GetCurrentUser().LastActivity = DateTime.Now;
        }

        private static ApplicationUser GetCurrentUser()
        {
            return config.Users.Where(x=> x.UserName == HttpContext.Current.User.Identity.Name).FirstOrDefault();
        }

        private static ICameraSerialPort GetSerialManagerForProtocol(AppConstants.CommandType type)
        {
            return SerialPortFactory.GetProtocolInterpreter(type);
        }
    }
}
